package spike.pool.implementations;

import spike.pool.interfaces.ConnectionPool;
import spike.pool.interfaces.ConnectionSource;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class PublicConnectionPool<T> implements ConnectionPool<T> {

    private ConnectionSource<T> connectionSource;
    private int min, max;
    private long keepAliveTime;
    private TimeUnit timeUnit;

    private Semaphore semaphore;
    private BlockingQueue<PublicConnectionSource<T>> connections;
    private Thread delayThread;

    public PublicConnectionPool(int min, int max, long keepAliveTime, TimeUnit timeUnit, ConnectionSource<T> connectionSource) {
        this.connectionSource = connectionSource;
        this.min = min;
        this.max = max;
        this.keepAliveTime = keepAliveTime;
        this.timeUnit = timeUnit;

        semaphore = new Semaphore(max, true);
        connections = new LinkedBlockingQueue<PublicConnectionSource<T>>();

        initializePool(min, keepAliveTime, connectionSource);
    }

    public int getAvailablePermits() {
        return semaphore.availablePermits();
    }

    public int getPoolSize() {
        return connections.size();
    }

    private void initializePool(int min, long keepAliveTime, ConnectionSource<T> connectionSource) {
        for (int i = 0; i < min; i++) {
            connections.offer(new PublicConnectionSource<>(connectionSource.open()));
        }

        delayThread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(keepAliveTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (connections.size() > min) {
                    connections.poll();
                }
            }
        });
        delayThread.setDaemon(true);
        delayThread.start();
    }


    private T getResource() {
        PublicConnectionSource<T> connSource;
//        while ((connSource = connections.poll()) == null) {
//            connections.offer(new PublicConnectionSource<>(connectionSource.open()));
//        }
//
//        return connSource.getConnection();

        if (connections.isEmpty()) {
            connections.offer(new PublicConnectionSource<>(connectionSource.open()));
        }
        PublicConnectionSource<T> cs = connections.peek();
        connections.remove(cs);
        return cs.getConnection();

    }

    @Override
    public T get() throws InterruptedException {
        semaphore.acquire();
        return getResource();
    }


    @Override
    public T tryGet() {
        if (semaphore.tryAcquire(1)) {
            return getResource();
        }
        return null;
    }

    @Override
    public T tryGet(long time, TimeUnit unit) throws InterruptedException {
        if (semaphore.tryAcquire(time, unit)) {
            return getResource();
        }
        return null;
    }

    @Override
    public void release(T src) {
        connections.offer(new PublicConnectionSource<>(src));
        semaphore.release();
    }

}
